//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.WFServiceLibrary.SchedulerLogic
{
    using System;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using V1 = TaskScheduler.V1;
    using V2 = TaskScheduler;

    /// <summary>
    /// Provides the methods that are used to run the task immediately, 
    /// get any running instances of the task, get or set the credentials that are used 
    /// to register the task, and the properties that describe the task.
    /// </summary>
    public class Task : IDisposable
    {
        private V2.IRegisteredTask v2Task;
        private static readonly DateTime taskInvalidDate = new DateTime(1899, 12, 30);
        internal V1.ITask v1Task;

        /// <summary>
        /// Initializes a new instance of the <see cref="Task"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        internal Task(V1.ITask iTask)
        {
            this.v1Task = iTask;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Task"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        internal Task(V2.IRegisteredTask iTask)
        {
            this.v2Task = iTask;
        }

        /// <summary>
        /// Gets the definition of the task.
        /// </summary>
        public TaskDefinition Definition
        {
            get
            {
                TaskDefinition taskDef = null;
                if (this.v2Task != null)
                    taskDef = new TaskDefinition(this.v2Task.Definition);

                return taskDef;
            }
        }

        /// <summary>
        /// Gets the name of the registered task.
        /// </summary>
        public string Name
        {
            get
            {
                if (this.v2Task != null)
                    return this.v2Task.Name;

                return System.IO.Path.GetFileNameWithoutExtension(GetV1Path(this.v1Task));
            }
        }

        /// <summary>
        /// Gets the time the registered task was last run.
        /// </summary>
        public DateTime LastRunTime
        {
            get
            {
                if (this.v2Task != null)
                {
                    DateTime dt = this.v2Task.LastRunTime;
                    return dt == taskInvalidDate ? DateTime.MinValue : dt;
                }

                return this.v1Task.GetMostRecentRunTime();
            }
        }

        /// <summary>
        /// Covert Timespan to string.
        /// </summary>
        /// <param name="span">The span.</param>
        /// <returns></returns>
        internal static string TimeSpanToString(TimeSpan span)
        {
            if (span == TimeSpan.Zero) return null;

            System.Text.StringBuilder sb = new System.Text.StringBuilder("P", 20);
            if (span.Days > 0) sb.AppendFormat("{0}D", span.Days);
            sb.Append('T');
            if (span.Hours > 0) sb.AppendFormat("{0}H", span.Hours);
            if (span.Minutes > 0) sb.AppendFormat("{0}M", span.Minutes);
            if (span.Seconds > 0) sb.AppendFormat("{0}S", span.Seconds);

            return sb.ToString();
        }

        /// <summary>
        /// Gets the v1 path.
        /// </summary>
        /// <param name="v1Task">The v1 task.</param>
        /// <returns></returns>
        internal static string GetV1Path(V1.ITask v1Task)
        {
            string fileName = string.Empty;
            try
            {
                IPersistFile iFile = (IPersistFile)v1Task;
                iFile.GetCurFile(out fileName);
            }
            catch (Exception)
            {
            }

            return fileName;
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, 
        /// or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (this.v2Task != null)
                Marshal.ReleaseComObject(this.v2Task);
            this.v1Task = null;
        }

        #endregion
    }

    /// <summary>
    /// Provides the methods to get information from and control a running task.
    /// </summary>
    public sealed class RunningTask : Task
    {
        private V2.IRunningTask taskRunningTask;

        /// <summary>
        /// Initializes a new instance of the <see cref="RunningTask"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        /// <param name="iRunningTask">The i running task.</param>
        internal RunningTask(V2.IRegisteredTask iTask, V2.IRunningTask iRunningTask)
            : base(iTask)
        {
            this.taskRunningTask = iRunningTask;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RunningTask"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        internal RunningTask(V1.ITask iTask)
            : base(iTask)
        {
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public new void Dispose()
        {
            base.Dispose();
            if (this.taskRunningTask != null) Marshal.ReleaseComObject(this.taskRunningTask);
        }

        /// <summary>
        /// Gets the GUID identifier for this instance of the task.
        /// </summary>
        public Guid InstanceGuid
        {
            get
            {
                Guid instanceId = Guid.Empty;
                if (this.taskRunningTask != null)
                    instanceId = new Guid(this.taskRunningTask.InstanceGuid);

                return instanceId;
            }
        }

        /// <summary>
        /// Gets the name of the current action that the running task is performing.
        /// </summary>
        public string CurrentAction
        {
            get
            {
                string currentAct = string.Empty; ;

                if (this.taskRunningTask != null)
                {
                    currentAct = this.taskRunningTask.CurrentAction;
                }
                else
                {
                    currentAct = base.v1Task.GetApplicationName();
                }

                return currentAct;
            }
        }

        /// <summary>
        /// Refreshes all of the local instance variables of the task.
        /// </summary>
        public void Refresh()
        {
            if (this.taskRunningTask != null)
                this.taskRunningTask.Refresh();
        }

        /// <summary>
        /// Gets the process ID for the engine (process) which is running the task.
        /// </summary>
        public uint EnginePID
        {
            get
            {
                uint engineId = 0;
                if (this.taskRunningTask != null)
                    engineId = this.taskRunningTask.EnginePID;

                return engineId;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public sealed class TaskDefinition
    {
        internal V1.ITask v1Task = null;
        private TaskSettings settings = null;
        internal string v1Name = string.Empty;

        /// <summary>
        /// 
        /// </summary>
        internal V2.ITaskDefinition taskDefinition = null;

        /// <summary>
        /// 
        /// </summary>
        private TaskRegistrationInfo regInfo = null;

        /// <summary>
        /// 
        /// </summary>
        private TaskPrincipal principal = null;

        /// <summary>
        /// 
        /// </summary>
        private ActionCollection actions = null;

        /// <summary>
        /// 
        /// </summary>
        private TriggerCollection triggers = null;

        /// <summary>
        /// Gets a class instance of registration information that is used to describe a task, such as the description of the task, the author of the task, and the date the task is registered.
        /// </summary>
        public TaskRegistrationInfo RegistrationInfo
        {
            get
            {
                if (this.regInfo == null)
                {
                    if (this.taskDefinition != null)
                    {
                        this.regInfo = new TaskRegistrationInfo(this.taskDefinition.RegistrationInfo);
                    }
                    else
                        this.regInfo = new TaskRegistrationInfo(v1Task);
                }

                return this.regInfo;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskDefinition"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        /// <param name="name">The name.</param>
        internal TaskDefinition(V1.ITask iTask, string name)
        {
            this.v1Task = iTask;
            v1Name = name;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskDefinition"/> class.
        /// </summary>
        /// <param name="iDef">The i def.</param>
        internal TaskDefinition(V2.ITaskDefinition iDef)
        {
            this.taskDefinition = iDef;
        }

        /// <summary>
        /// Gets a collection of triggers that are used to start a task.
        /// </summary>
        public TriggerCollection Triggers
        {
            get
            {
                if (this.triggers == null)
                {
                    if (this.taskDefinition != null)
                    {
                        this.triggers = new TriggerCollection(this.taskDefinition);
                    }
                    else
                    {
                        this.triggers = new TriggerCollection(this.v1Task);
                    }
                }

                return this.triggers;
            }
        }

        /// <summary>
        /// Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by third-parties who wish to extend the task format.
        /// </summary>
        public string Data
        {
            get
            {
                if (this.taskDefinition != null)
                    return this.taskDefinition.Data;
                return TaskRegistrationInfo.GetTaskData(this.v1Task).ToString();
            }
            set
            {
                if (this.taskDefinition != null)
                    this.taskDefinition.Data = value;
                else
                    TaskRegistrationInfo.SetTaskData(this.v1Task, value);
            }
        }

        /// <summary>
        /// Gets the principal for the task that provides the security credentials for the task.
        /// </summary>
        public TaskPrincipal Principal
        {
            get
            {
                if (this.principal == null)
                {
                    if (this.taskDefinition != null)
                    {
                        this.principal = new TaskPrincipal(this.taskDefinition.Principal);
                    }
                    else
                    {
                        this.principal = new TaskPrincipal(this.v1Task);
                    }
                }

                return this.principal;
            }
        }

        /// <summary>
        /// Gets a collection of actions that are performed by the task.
        /// </summary>
        public ActionCollection Actions
        {
            get
            {
                if (this.actions == null)
                {
                    if (this.taskDefinition != null)
                    {
                        this.actions = new ActionCollection(this.taskDefinition);
                    }
                    else
                    {
                        this.actions = new ActionCollection(this.v1Task);
                    }
                }

                return this.actions;
            }
        }

        /// <summary>
        /// Gets the settings that define how the Task Scheduler service performs the task.
        /// </summary>
        public TaskSettings Settings
        {
            get
            {
                if (this.settings == null)
                {
                    if (this.taskDefinition != null)
                        this.settings = new TaskSettings(this.taskDefinition.Settings);
                    else
                        this.settings = new TaskSettings(this.v1Task);
                }
                return this.settings;
            }
        }

        internal void V1Save(string newName)
        {
            if (this.v1Task != null)
            {
                this.Triggers.Bind();

                IPersistFile iFile = (IPersistFile)this.v1Task;
                if (string.IsNullOrEmpty(newName) || newName == v1Name)
                    iFile.Save(null, false);
                else
                {
                    string path;
                    iFile.GetCurFile(out path);
                    System.IO.File.Delete(path);
                    iFile.Save(
                        System.IO.Path.GetDirectoryName(path) +
                        System.IO.Path.DirectorySeparatorChar +
                        newName +
                        System.IO.Path.GetExtension(path), true);
                }
                iFile = null;
            }
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public void Dispose()
        {
            this.regInfo = null;
            if (this.taskDefinition != null)
            {
                Marshal.ReleaseComObject(this.taskDefinition);
            }

            this.v1Task = null;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public sealed class TaskPrincipal
    {
        private V1.ITask v1Task = null;

        /// <summary>
        /// 
        /// </summary>
        private V2.IPrincipal taskPrincipal;

        internal TaskPrincipal(V1.ITask iTask)
        {
            this.v1Task = iTask;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskPrincipal"/> class.
        /// </summary>
        /// <param name="iPrincipal">The i principal.</param>
        internal TaskPrincipal(V2.IPrincipal iPrincipal)
        {
            this.taskPrincipal = iPrincipal;
        }

        /// <summary>
        /// Gets or sets the identifier of the principal.
        /// </summary>
        public string Id
        {
            get
            {
                if (this.taskPrincipal != null)
                    return this.taskPrincipal.UserId;
                return "Guest";
            }
            set
            {
                if (this.taskPrincipal != null)
                    this.taskPrincipal.UserId = value;
                else
                    throw new InvalidProgramException();
            }
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public void Dispose()
        {
            if (this.taskPrincipal != null)
            {
                Marshal.ReleaseComObject(this.taskPrincipal);
            }

            this.v1Task = null;
        }

        /// <summary>
        /// Gets or sets the security logon method that is required to run the tasks that are associated with the principal.
        /// </summary>
        public V2._TASK_LOGON_TYPE LogonType
        {
            get
            {
                if (this.taskPrincipal != null)
                {
                    return this.taskPrincipal.LogonType;
                }

                if ((this.v1Task.GetFlags() & V1.TaskFlags.Interactive) == V1.TaskFlags.Interactive)
                    return V2._TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN;

                throw new NotSupportedException();
            }

            set
            {
                if (this.taskPrincipal != null)
                {
                    this.taskPrincipal.LogonType = value;
                }
                else
                {
                    V1.TaskFlags flags = this.v1Task.GetFlags();
                    if (value == V2._TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN)
                        flags |= V1.TaskFlags.Interactive;
                    else
                        flags &= ~V1.TaskFlags.Interactive;
                    v1Task.SetFlags(flags);
                    if (value == V2._TASK_LOGON_TYPE.TASK_LOGON_GROUP ||
                        value == V2._TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD ||
                        value == V2._TASK_LOGON_TYPE.TASK_LOGON_NONE ||
                        value == V2._TASK_LOGON_TYPE.TASK_LOGON_PASSWORD ||
                        value == V2._TASK_LOGON_TYPE.TASK_LOGON_S4U)
                        throw new NotSupportedException();
                }
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public sealed class TaskRegistrationInfo : IDisposable
    {
        private V1.ITask v1Task = null;

        /// <summary>
        /// 
        /// </summary>
        private V2.IRegistrationInfo taskRegInfo = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskRegistrationInfo"/> class.
        /// </summary>
        /// <param name="iRegInfo">The i reg info.</param>
        internal TaskRegistrationInfo(V2.IRegistrationInfo iRegInfo)
        {
            this.taskRegInfo = iRegInfo;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskRegistrationInfo"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        internal TaskRegistrationInfo(V1.ITask iTask)
        {
            v1Task = iTask;
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public void Dispose()
        {
            v1Task = null;
            if (this.taskRegInfo != null)
            {
                Marshal.ReleaseComObject(this.taskRegInfo);
            }
        }

        /// <summary>
        /// Gets or sets the description of the task.
        /// </summary>
        public string Description
        {
            get
            {
                if (this.taskRegInfo != null)
                {
                    return this.taskRegInfo.Description;
                }

                return v1Task.GetComment();
            }
            set
            {
                if (this.taskRegInfo != null)
                {
                    this.taskRegInfo.Description = value;
                }
                else
                {
                    v1Task.SetComment(value);
                }
            }
        }

        /// <summary>
        /// Gets the task data.
        /// </summary>
        /// <param name="v1Task">The v1 task.</param>
        /// <returns></returns>
        internal static object GetTaskData(V1.ITask v1Task)
        {
            ushort DataLen;
            IntPtr Data;
            try
            {
                v1Task.GetWorkItemData(out DataLen, out Data);
                byte[] bytes = new byte[DataLen];
                Marshal.Copy(Data, bytes, 0, DataLen);
                System.IO.MemoryStream stream = new System.IO.MemoryStream(bytes, false);
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter b =
                    new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                return b.Deserialize(stream);
            }
            catch { }
            return string.Empty;
        }

        /// <summary>
        /// Sets the task data.
        /// </summary>
        /// <param name="v1Task">The v1 task.</param>
        /// <param name="value">The value.</param>
        internal static void SetTaskData(V1.ITask v1Task, object value)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter b =
                new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            b.Serialize(stream, value);
            v1Task.SetWorkItemData((ushort)stream.Length, stream.GetBuffer());
        }
    }

    /// <summary>
    /// TAsk setting class.
    /// </summary>
    public sealed class TaskSettings : IDisposable
    {
        private V1.ITask v1Task = null;
        private V2.ITaskSettings v2Settings = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskSettings"/> class.
        /// </summary>
        /// <param name="iSettings">The i settings.</param>
        internal TaskSettings(V2.ITaskSettings iSettings)
        {
            this.v2Settings = iSettings;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TaskSettings"/> class.
        /// </summary>
        /// <param name="iTask">The i task.</param>
        internal TaskSettings(V1.ITask iTask)
        {
            this.v1Task = iTask;
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public void Dispose()
        {
            if (this.v2Settings != null)
                Marshal.ReleaseComObject(this.v2Settings);
            this.v1Task = null;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="TaskSettings"/> is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public bool Enabled
        {
            get
            {
                if (this.v2Settings != null)
                    return this.v2Settings.Enabled;
                return (this.v1Task.GetFlags() & V1.TaskFlags.Disabled) != V1.TaskFlags.Disabled;
            }
            set
            {
                if (this.v2Settings != null)
                    this.v2Settings.Enabled = value;
                else
                {
                    V1.TaskFlags flags = this.v1Task.GetFlags();
                    if (!value)
                        this.v1Task.SetFlags(flags |= V1.TaskFlags.Disabled);
                    else
                        this.v1Task.SetFlags(flags &= ~V1.TaskFlags.Disabled);
                }
            }
        }
    }

    /// <summary>
    /// Defines how the Task Scheduler service creates, updates, or disables the task.
    /// </summary>
    public enum TaskCreation
    {
        /// <summary>
        /// The Task Scheduler service registers the task as a new task.
        /// </summary>
        Create = 2,

        /// <summary>
        /// The Task Scheduler service either registers the task as a new task or as an updated version if the task already exists. Equivalent to Create | Update.
        /// </summary>
        CreateOrUpdate = 6,

        /// <summary>
        /// The Task Scheduler service registers the disabled task. A disabled task cannot run until it is enabled. For more information, see Enabled Property of TaskSettings and Enabled Property of RegisteredTask.
        /// </summary>
        Disable = 8,

        /// <summary>
        /// The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal. When the TaskFolder.RegisterTaskDefinition or TaskFolder.RegisterTask functions are called with this flag to update a task, the Task Scheduler service does not add the ACE for the new context principal and does not remove the ACE from the old context principal.
        /// </summary>
        DontAddPrincipalAce = 0x10,

        /// <summary>
        /// The Task Scheduler service creates the task, but ignores the registration triggers in the task. By ignoring the registration triggers, the task will not execute when it is registered unless a time-based trigger causes it to execute on registration.
        /// </summary>
        IgnoreRegistrationTriggers = 0x20,

        /// <summary>
        /// The Task Scheduler service registers the task as an updated version of an existing task. When a task with a registration trigger is updated, the task will execute after the update occurs.
        /// </summary>
        Update = 4,

        /// <summary>
        /// The Task Scheduler service checks the syntax of the XML that describes the task but does not register the task. This constant cannot be combined with the Create, Update, or CreateOrUpdate values.
        /// </summary>
        ValidateOnly = 1
    }
}
